package com.cloud.common.util;

import com.cloud.common.validator.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.cglib.beans.BeanCopier;

import javax.validation.constraints.Email;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 对象工具类
 *
 * @author fengwenjin
 */
@Slf4j
public class ObjectUtils {

    public static <S, T> T copy(S source, Class<T> targetClass) {
        if (source == null) {
            return null;
        }
        try {
            T target = targetClass.newInstance();
            BeanCopier beanCopier = BeanCopier.create(source.getClass(), targetClass, false);
            beanCopier.copy(source, target, null);
            return target;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    public static <S, T> List<T> copy(List<S> list, Class<T> targetClass) {
        if (list == null) {
            return null;
        }
        return list.stream().map(source -> copy(source, targetClass)).collect(Collectors.toList());
    }

    public static String[] getNullProperties(Object bean) {
        BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
        PropertyDescriptor[] descriptors = beanWrapper.getPropertyDescriptors();
        List<String> list = new ArrayList<>();
        for (PropertyDescriptor descriptor : descriptors) {
            String name = descriptor.getName();
            if (beanWrapper.getPropertyValue(name) == null) {
                list.add(name);
            }
        }
        String[] result = new String[list.size()];
        return list.toArray(result);
    }

    /**
     * 构建实例并设置默认值
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T buildInstance(Class<T> clazz) {
        if (clazz == null) {
            return null;
        }
        if (clazz == List.class || clazz == Set.class) {
            return null;
        }
        T value = getBasicValue(clazz, null);
        if (value != null) {
            return value;
        }
        try {
            T instance = clazz.newInstance();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }
                Class fieldClass = field.getType();
                Object basicValue = getBasicValue(fieldClass, field);
                if (basicValue != null) {
                    field.set(instance, basicValue);
                    continue;
                }
                if (fieldClass == List.class) {
                    Class genericClazz = getGenericClass(field);
                    if (genericClazz != null) {
                        field.set(instance, Collections.singletonList(buildInstance(genericClazz)));
                    }
                    continue;
                }
                if (fieldClass == Set.class) {
                    Class genericClazz = getGenericClass(field);
                    if (genericClazz != null) {
                        field.set(instance, Collections.singleton(buildInstance(genericClazz)));
                    }
                    continue;
                }
                field.set(instance, buildInstance(fieldClass));
            }
            return instance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static <T> T getBasicValue(Class<T> clazz, Field field) {
        final String defaultNumberStr = "1";
        if (clazz == Short.class || clazz == short.class) {
            return (T) Short.valueOf(defaultNumberStr);
        }
        if (clazz == Integer.class || clazz == int.class) {
            return (T) Integer.valueOf(defaultNumberStr);
        }
        if (clazz == Long.class || clazz == long.class) {
            return (T) Long.valueOf(defaultNumberStr);
        }
        if (clazz == Float.class || clazz == float.class) {
            return (T) Float.valueOf(defaultNumberStr);
        }
        if (clazz == Double.class || clazz == double.class) {
            return (T) Double.valueOf(defaultNumberStr);
        }
        if (clazz == Boolean.class || clazz == boolean.class) {
            return (T) Boolean.valueOf(false);
        }
        if (clazz == Date.class) {
            return (T) DateTimeUtils.asDate(LocalDate.now());
        }
        if (clazz == String.class) {
            return (T) buildString(field);
        }
        return null;
    }

    private static String buildString(Field field) {
        if (field != null) {
            // 邮箱格式
            Annotation annotation = field.getAnnotation(Email.class);
            if (annotation != null) {
                return "test@github.com";
            }
            // 手机号格式
            annotation = field.getAnnotation(PhoneNumberCheck.class);
            if (annotation != null) {
                return "15866666666";
            }
            // 身份证格式
            annotation = field.getAnnotation(IdCardCheck.class);
            if (annotation != null) {
                return "440303199209122730";
            }
            // 日期格式
            annotation = field.getAnnotation(DateCheck.class);
            if (annotation != null) {
                return DateTimeUtils.toDateString(LocalDate.now());
            }
            annotation = field.getAnnotation(DateTimeCheck.class);
            if (annotation != null) {
                return DateTimeUtils.toDateTimeString(DateTimeUtils.asDate(LocalDate.now()));
            }
            annotation = field.getAnnotation(TimeCheck.class);
            if (annotation != null) {
                return LocalTime.of(10, 30, 10).toString();
            }
        }
        return "example";
    }

    private static Class getGenericClass(Field field) throws Exception {
        Type genericType = field.getGenericType();
        if (genericType != null && genericType instanceof ParameterizedType) {
            String typeName = ((ParameterizedType) genericType).getActualTypeArguments()[0].getTypeName();
            return Class.forName(typeName);
        }
        return null;
    }
}
